<html>
    <head>
        <title>功能管理 Features Managerment</title>
    </head>
    <body>
        <script>
            // 功能管理

                    /*
                        ABP 功能系统用于在运行时 启用、禁用 或 更改应用程序功能的行为。

                        功能的"运行时值" 通常是 boolean 值，。但是你可以获取/设置任何类型的功能值。

                        功能系统最初设计用于控制多租户应用程序中的"租户功能"。但是，它是"可以扩展的"并且能够通过任何条件确定功能。

                        该功能系统使用 "Volo.Abp.Feature" 包实现。大多数时候，你不需要手动安装，因为他已经预先安装在启动模板中了。
                    */


                    /*
                        一、检查功能

                                   在解释定义功能之前，让我们看看如何检查应用程序代码中的功能值。


                                   1、使用[RequiredFeature] 特性进行功能检查

                                                [RequiredFeature] 特性 （在Volo.Abp.Features命名空间中定义）用于以声明方式检查功能是否为true（已启用）。

                                                它是 boolean 功能的有用的快捷方式。

                                                eg：  检查是否启用了"PDF" 报告功能

                                                        public class ReportingAppService : ApplicationService, IReportingAppService
                                                        {
                                                            [RequiresFeature("MyApp.PdfReporting")]  // 检查是否启用了"PDF" 报告功能
                                                            public async Task<PdfReportResultDto> GetPdfReportAsync()  // 拦截系统只拦截 异步方法！！
                                                            {
                                                                //TODO...
                                                            }
                                                        }


                                                RequiresFeature(...)只需获取一个功能名称来检查它是否已启用。如果未启用，则抛出"授权异常"并向客户端返回正确的响应。

                                                RequiresFeature可以获得多个功能名称，如 [RequiresFeature("Feature1", "Feature2")]。  在这种情况下，ABP检查是否启用了要求的所有功能。
                                                使用RequiresAll选项，如[RequiresFeature("Feature1", "Feature2", RequiresAll = true)]强制检查所有要启用的功能。

                                                方法或类支持多次使用 [RequiresFeature]属性。在这种情况下，ABP会检查所有这些。

                                            
                                                ★、功能名称可以是任意字符串。对于一个功能来说，它应该是唯一的。

                                                ★、关于拦截系统

                                                        ABP使用拦截系统来是 [RequiresFeature]属性工作。因此，它可以与从一来注入的任何类(应用程序服务、控制器)一起使用。

                                                        但是，需要遵循一些规则才能使其工作：

                                                                ①、 如果你没有通过接口注入服务(如，IMyService)，则服务的方法必须是virtual。否则，动态代理/拦截系统无法工作。

                                                                ②、仅拦截 async 方法

                                                (控制器 和 Razor页面方法有一个例外。它们不需要遵循上述规则，因为ABP在这种情况下使用操作/页面过滤器来实现功能检查。)


                            2、使用 IFeatureChecker 服务检查功能

                                                 IFeatureChecker 允许检查应用程序代码中的功能。

                                        ①、IsEnabledAsync方法
                                                                
                                                如果启用了给定功能，则返回true。因此，你可以有条件地执行你的业务流程。

                                                eg:  检查是否启用了"PDF" 报告功能

                                                    public class ReportingAppService : ApplicationService, IReportingAppService
                                                    {
                                                        private readonly IFeatureChecker _featureChecker;

                                                        public ReportingAppService(IFeatureChecker featureChecker)
                                                        {
                                                            _featureChecker = featureChecker;  // 注入 IFeatureChecker  功能检查服务
                                                        }

                                                        public async Task<PdfReportResultDto> GetPdfReportAsync()
                                                        {
                                                            if (await _featureChecker.IsEnabledAsync("MyApp.PdfReporting"))  // 检查是否启用了"PDF" 报告功能
                                                            {
                                                                //TODO...
                                                            }
                                                            else
                                                            {
                                                                //TODO...   
                                                            }
                                                        }
                                                    }


                                                IsEnabledAsync具有重载以检查一个方法调用中的多个功能。


                                        ②、GetOrNullAsync 方法

                                                获取功能的当前值。一方法返回一个 string ,因此你可以通过与string相互转换来在其中存储任何类型的值。

                                                eg： 检查允许的最大产品计数：

                                                    public class ProductController : AbpController
                                                    {
                                                        private readonly IFeatureChecker _featureChecker;

                                                        public ProductController(IFeatureChecker featureChecker)
                                                        {
                                                            _featureChecker = featureChecker; // 注入 IFeatureChecker 功能检查服务
                                                        }

                                                        public async Task<IActionResult> Create(CreateProductModel model)
                                                        {
                                                            var currentProductCount = await GetCurrentProductCountFromDatabase();

                                                            //GET THE FEATURE VALUE
                                                            var maxProductCountLimit =
                                                                await _featureChecker.GetOrNullAsync("MyApp.MaxProductCount"); // GetOrNullAsync 返回一个string

                                                            if (currentProductCount >= Convert.ToInt32(maxProductCountLimit))
                                                            {
                                                                throw new BusinessException(
                                                                    "MyApp:ReachToMaxProductCountLimit",
                                                                    $"You can not create more than {maxProductCountLimit} products!"
                                                                );
                                                            }

                                                            //TODO: Create the product in the database...
                                                        }

                                                        private async Task<int> GetCurrentProductCountFromDatabase()
                                                        {
                                                            throw new System.NotImplementedException();
                                                        }
                                                    }

                                                此示例使用数值作为SaaS应用程序中用户/租户的功能限制产品计数

                                                你可以使用GetAsync方法的通用重载，而不是手动将值转换为int：

                                                            var maxProductCountLimit = await _featureChecker.GetAsync<int>("MyApp.MaxProductCount");
                                        
                                        
                                        ③、扩展方法

                                                IFeatureChecker 接口有一些有用的扩展方法：

                                                            Task<T> GetAsync<T>(string name, T defaultValue = default)：
                                                                        用于获取给定类型T的特征的值。允许指定当特征值为null时返回的defaultValue。

                                                            CheckEnabledAsync(string name)：
                                                                        检查给定功能是否已启用。如果该功能不是true（已启用），则抛出AbpAuthorizationException。

                                                           
                    */

                    /*
                        二、定义功能
                                                                
                                  一个功能应该先被定义，然后才能检查它。

                                  1、功能定义程序  FeatureDefinitionProvider基类
                                        
                                            创建一个继承自 FeatureDefinitionProvider 的类来定义功能。

                                            eg：  定义功能

                                                using Volo.Abp.Features;

                                                namespace FeaturesDemo
                                                {
                                                    public class MyFeatureDefinitionProvider : FeatureDefinitionProvider // 继承自 FeatureDefinitionProvider基类
                                                    {
                                                        // 重写功能定义方法
                                                        public override void Define(IFeatureDefinitionContext context) // 功能定义上下文对象
                                                        {
                                                            var myGroup = context.AddGroup("MyApp"); // 添加一个功能组

                                                            myGroup.AddFeature(  // 向功能组中添加一个功能
                                                                "MyApp.PdfReporting", 
                                                                defaultValue: "false"
                                                            );
                                                            
                                                            myGroup.AddFeature(
                                                                "MyApp.MaxProductCount",
                                                                defaultValue: "10",
                                                                valueType: new FreeTextStringValueType(new NumericValueValidator())
                                                            );
                                                        }
                                                    }
                                                }


                                            ABP会自动发现此类并注册功能。无需额外配置。

                                            此类通常在解决方案的Application.Contracts（应用契约层）中创建。

                                            在Define方法中，你首先需要为你的应用程序/模块添加一个功能组或获取一个现有组，然后将功能添加到该组。

                                            第一个功能名为MyApp.PdfReporting，是一个boolean功能，false作为默认值。

                                            第二个功能名为MyApp.MaxProductCount，是一个以10为默认值的数字功能。

                                            如果没有为当前用户/租户设置其他值，则使用默认值。
                                        

                                2、其他功能属性  
                                
                                        虽然这些最小定义足以是功能系统正常工作，但你可以为功能指定可选的属性：

                                            ①、DisplayName：  用于在用户交互界面上显示功能名称的可本地化字符串。

                                            ②、Description： 用于描述功能的较长可本地化文本。

                                            ③、ValueType: 功能值得类型。   可以是实现IStringValueType 的类。

                                                            ToggleStringValueType：用于定义true/false，on/off，enabled/disabled样式功能。UI上显示一个复选框。

                                                            FreeTextStringValueType：用于定义自由文本值。UI上显示一个文本框。

                                                            SelectionStringValueType：用于强制从列表中选择值。UI上显示下拉列表。

                                            ④、IsVisibleToClients（默认值：true）：
                                                    
                                                        设置false以向客户端（浏览器）隐藏此功能的值。与客户端共享该值有助于他们根据特征值有条件地显示/隐藏/更改UI部分。

                                            ⑤、Properties：
                                                        用于设置/获取与此功能相关的任意键值对的字典。这可以是自定义的一个点。


                                        因此，基于这些描述，最好定义这些功能，如下所示：
                                                                
                                                    using FeaturesDemo.Localization;
                                                    using Volo.Abp.Features;
                                                    using Volo.Abp.Localization;
                                                    using Volo.Abp.Validation.StringValues;

                                                    namespace FeaturesDemo
                                                    {
                                                        public class MyFeatureDefinitionProvider : FeatureDefinitionProvider
                                                        {
                                                            public override void Define(IFeatureDefinitionContext context)
                                                            {
                                                                var myGroup = context.AddGroup("MyApp");

                                                                myGroup.AddFeature(
                                                                    "MyApp.PdfReporting",
                                                                    defaultValue: "false",
                                                                    displayName: LocalizableString
                                                                                    .Create<FeaturesDemoResource>("PdfReporting"),  // FeaturesDemoResource是此示例代码中的项目名称。有关本地化系统的详细信息，请参阅本地化文档。
                                                                    valueType: new ToggleStringValueType()
                                                                );

                                                                myGroup.AddFeature(
                                                                    "MyApp.MaxProductCount",
                                                                    defaultValue: "10",
                                                                    displayName: LocalizableString
                                                                                    .Create<FeaturesDemoResource>("MaxProductCount"),
                                                                    valueType: new FreeTextStringValueType(
                                                                                new NumericValueValidator(0, 1000000))

                                                                                第一个功能设置为ToggleStringValueType，而第二个功能设置为FreeTextStringValueType，带有一个数字验证器，允许从0到1,000,000的值。
                                                                );
                                                            }
                                                        }
                                                    }   

                                        请记住在本地化文件中定义本地化键：

                                                    "PdfReporting": "PDF Reporting",
                                                    "MaxProductCount": "Maximum number of products"
                                 
                                                    
                                3、功能管理模式

                                        应用程序启动模板预装了"租户管理" 和 "功能管理"模块。

                                        每当你定义新功能时，它都将在 "功能管理模式"上可用。

                                        要打开此模式，请导航到租户管理页面并为组化选择Features操作(如果还没有租户，请创建一个新租户)。


                                        因此，你可以为租户启用、禁用 和 设置值。每当此租户的用户使用应用程序时，都会使用这些值。


                                4、子功能  Child Features

                                        一个功能可能有子功能。如果你想创建一个仅在另一个功能可选择的功能，这将特别有用。

                                        eg: 定义子功能

                                            using FeaturesDemo.Localization;
                                            using Volo.Abp.Features;
                                            using Volo.Abp.Localization;
                                            using Volo.Abp.Validation.StringValues;

                                            namespace FeaturesDemo
                                            {
                                                public class MyFeatureDefinitionProvider : FeatureDefinitionProvider
                                                {
                                                    public override void Define(IFeatureDefinitionContext context)
                                                    {
                                                        var myGroup = context.AddGroup("MyApp");

                                                        var reportingFeature = myGroup.AddFeature(
                                                            "MyApp.Reporting",
                                                            defaultValue: "false",
                                                            displayName: LocalizableString
                                                                            .Create<FeaturesDemoResource>("Reporting"),
                                                            valueType: new ToggleStringValueType()
                                                        );

                                                        reportingFeature.CreateChild(  // 添加子功能
                                                            "MyApp.PdfReporting",
                                                            defaultValue: "false",
                                                            displayName: LocalizableString  // 界面展示名称
                                                                            .Create<FeaturesDemoResource>("PdfReporting"),
                                                            valueType: new ToggleStringValueType()
                                                        );

                                                        reportingFeature.CreateChild(  // 添加子功能
                                                            "MyApp.ExcelReporting",
                                                            defaultValue: "false",
                                                            displayName: LocalizableString
                                                                            .Create<FeaturesDemoResource>("ExcelReporting"),
                                                            valueType: new ToggleStringValueType()
                                                        );
                                                    }
                                                }
                                            }

                                    上面的示例定义了带有两个子项的Report功能： PDF Report 和 Excel Report
                                    
                                    
                            5、更改依赖模块中的功能定义

                                    从 FeatureDefinitionProvider 派生的类（就像上面的例子一样）也可以获取现有的功能定义（由依赖的模块定义）并更改它们的定义。

                                    eg: 操作现有特征定义

                                            var someGroup = context.GetGroupOrNull("SomeModule"); // 获取现有功能组
                                            var feature = someGroup.Features.FirstOrDefault(f => f.Name == "SomeFeature"); // 获取现有功能组中现有的功能
                                            if (feature != null)
                                            {
                                                feature.Description = ...
                                                feature.CreateChild(...);
                                            }
                    */


                    /*
                        三、客户端中检查功能   Check a Feature in the Client Side

                                功能值在客户端也可用，除非你在功能定义上将 IsVisibleToClients 设置为 false.

                                功能值从  Application Configuration API 公开，并可通过UI上的某些服务使用。

                                请参阅一下文档以了解如何检查不同UI类型的功能：

                                        Aps.net core mvc/razor Pages:     https://abp.io/docs/latest/framework/ui/mvc-razor-pages/javascript-api/features                     
                    */

                    /*
                        四、功能管理

                            1、UI界面管理功能：

                                    功能管理通常由管理员用户使用功能管理模式完成：

                                    此模式在相关实体上可用，例如多租户应用程序的租户。
                                    
                                    要打开它，你导航到"租户管理"页面(对于多租户应用程序)，单击租户左侧的Actions按钮并选择功能操作。

                            2、IFeatureManager 在代码中管理功能：
                            
                                    如果你需要通过代码管理功能，请注入 IFeatureManager 并使用它。

                                    eg: 为租户启用PDF报告

                                          public class MyService : ITransientDependency
                                        {
                                            private readonly IFeatureManager _featureManager;

                                            public MyService(IFeatureManager featureManager)
                                            {
                                                _featureManager = featureManager; // 注入IFeatureManager功能管理服务
                                            }

                                            public async Task EnablePdfReporting(Guid tenantId)
                                            {
                                                await _featureManager.SetForTenantAsync(  // 为租户设置功能值
                                                    tenantId,
                                                    "MyApp.PdfReporting",
                                                    true.ToString()
                                                );
                                            }
                                        }   
                                            
                                    IFeatureManager由功能管理模块定义。

                                    它与应用程序启动模板一起预装。有关详细信息，请参阅功能管理模块留档。
                */

                /*
                    五、高级主题

                            1、功能值提供程序  Feature Value Providers

                                    功能系统是可扩展的，任何派生自 FeatureValueProvider （或实现 IFeatureValueProvider）的类都可以为功能系统做出贡献，

                                    值提供程序负责"获取给定功能的当前值"。


                                    功能值提供程序"一个接一个地执行"。如果其中一个返回非空值，则使用该功能值，不执行其他提供程序。

                                    有三个预定义的值提供程序，按给定顺序执行：
                                                                            
                                        ①、TenantFeatureValueProvider：  尝试获取是否为当前租户显示设置了功能值。

                                        ②、EditionFeatureValueProvider：  获取当前版本的功能值。  
                                        
                                                    Edition Id是从当前主体标识（ICurrentPrincipalAccessor）中获取的，声明名称为editionid（定义为AbpClaimTypes.EditionId的常量）。
                                                    
                                                    版本不会为租户管理模块实现。你可以自己实现，也可以考虑使用ABP商业的SaaS模块。

                                        ③、DefaultValueFeatureValueProvider获取功能的默认值。


                                    你可以通过继承  FeatureValueProvider 来编写自己的提供程序。

                                    eg: 使用“SystemAdmin”作为“User_Type”声明值为用户启用所有功能:

                                                using System.Threading.Tasks;
                                                using Volo.Abp.Features;
                                                using Volo.Abp.Security.Claims;
                                                using Volo.Abp.Validation.StringValues;

                                                namespace FeaturesDemo
                                                {
                                                    public class SystemAdminFeatureValueProvider : FeatureValueProvider
                                                    {
                                                        public override string Name => "SA";

                                                        private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; // 当前主体访问器服务

                                                        public SystemAdminFeatureValueProvider(
                                                            IFeatureStore featureStore,
                                                            ICurrentPrincipalAccessor currentPrincipalAccessor)
                                                            : base(featureStore)
                                                        {
                                                            _currentPrincipalAccessor = currentPrincipalAccessor;  
                                                        }

                                                        public override Task<string> GetOrNullAsync(FeatureDefinition feature)
                                                        {
                                                            if (feature.ValueType is ToggleStringValueType &&
                                                                _currentPrincipalAccessor.Principal?.FindFirst("User_Type")?.Value == "SystemAdmin")
                                                            {
                                                                return Task.FromResult("true");
                                                            }

                                                            return null;
                                                        }
                                                    }
                                                }


                                        如果功能值提供程序返回null，则执行下一个提供程序。

                                        定义功能值提供程序后，应该将其添加到AbpFeatureOptions,如下所示：

                                                Configure<AbpFeatureOptions>(options =>
                                                {
                                                    options.ValueProviders.Add<SystemAdminFeatureValueProvider>();
                                                });

                                        在你的模块类的ConfigureServices中使用此代码。
                */
        </script>
    </body>
</html>